--[[
-- serialize.lua -- Serialization system for Lua
--
-- Copyright (C) 2007  Patrick J. Donnelly (batrick@unm.edu)
--
-- This software is distributed under the same license as Lua 5.0:
--
-- Permission is hereby granted, free of charge, to any person obtaining a
-- copy of this software and associated documentation files (the "Software"),
-- to deal in the Software without restriction, including without limitation
-- the rights to use, copy, modify, merge, publish, distribute, sublicense,
-- and/or sell copies of the Software, and to permit persons to whom the
-- Software is furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included
-- in all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
-- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
-- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-- OTHER DEALINGS IN THE SOFTWARE. 
--]]

local _G = _G;
local error = error;
local pairs = pairs;
local ipairs = ipairs;
local type = type;
local tonumber = tonumber;
local tostring = tostring;
local setmetatable = setmetatable;
local pcall = pcall;
local unpack = unpack;
local rawset = rawset;
local strdump = string.dump;
local huge = math.huge;
local getupvalue = debug.getupvalue or function() return nil end;
local concat = table.concat;
local stdout = io.stdout;
local stdin = io.stdin;
local stderr = io.stderr;

module("serialize");

--
--From DataDumpster, modified
--
local core_functions = {}
for _,lib in ipairs{'_G', 'string', 'table', 'math',
  'io', 'os', 'coroutine', 'debug'} do
  local t = _G[lib] or {}
  lib = lib .. "."
  if lib == "_G." then lib = "" end
  for k,v in pairs(t) do
    if type(v) == 'function' and not pcall(strdump, v) then
      core_functions[v] = lib..k
    end
  end
end


local REVERSE_T = {};
local MAX_SIZE = {};
local SIZE = {};

local function serialize_value(values, v, l)
  local tv = type(v);
  local serialized;
  if tv == "string" then
    serialized = ("%q"):format(v);
  elseif tv == "number" or tv == "boolean" then
    serialized = tostring(v);
  elseif tv == "table" then
    serialized = "\n"..l.." = {}";
    local s = {};
    local prefix = "\n"..l.."[";
    for vkey, vval in pairs(v) do
      s[#s+1] = prefix;
      s[#s+1] = values[vkey];
      s[#s+1] = "] = ";
      s[#s+1] = values[vval];
    end
    return serialized, concat(s);
  elseif tv == "function" then
    local c = core_functions[v];
    if c then
      serialized = c;
    else
      local b, dumped = pcall(strdump, v);
      if not b then -- unkown C function
        serialized = ("%q"):format(tostring(v));
      else
        serialized = ("\n"..l.." = loadstring(%q)"):format(dumped);
        if getupvalue(v, 1) ~= nil then
          local c = {"\nclose(", l};
          for i = 1, huge do
            local name, val = getupvalue(v, i);
            if name == nil then
              break;
            end
            c[#c+1] = ", ";
            c[#c+1] = val == nil and "nil" or values[val];
          end
          c[#c+1] = ")"
          return serialized, concat(c);
        end
        return serialized;
      end
    end
  else -- userdata/thread
    serialized = ("%q"):format(tostring(v));
  end
  return "\n"..l.." = "..serialized;
end

local values_m = {
  __index = function(t, k)
    local r = t[REVERSE_T];
    local l = #r+1;
    if tonumber(k) then
      t[k] = k;
    else
      r[l] = ""; -- placeholder
      t[k] = "t["..l.."]";
      local before, after = serialize_value(t, k, t[k]);
      r[l] = before;
      r[#r+1] = after;
    end
    return t[k];
  end,
  __newindex = function(t, k, v)
    t[SIZE] = t[SIZE] + 1;
    if t[SIZE] >= t[MAX_SIZE] then
      error("table has too many entries");
    else
      rawset(t, k, v);
    end
  end,
  __call = function(t, ...)
    local r = t[REVERSE_T];
    return unpack(r, 0);
  end,
};

--
-- function "dump"
-- parameters:
--     t = a table to serialize
--     name = the global name of the table, must not be "t"
--     file = file to write serialized data to
--     max = max raw values that can be serialized
--

function dump(t, name, file, max)
  local values = {
    [t] = name,
    [REVERSE_T] = {[0] = "local t = {}"},
    [MAX_SIZE] = max,
    [SIZE] = 0,
    [dump] = ("%q"):format(tostring(dump)), -- so it can't serialize itself
    [stdin] = "io.stdin",
    [stderr] = "io.stderr",
    [stdout] = "io.stdout",
  };
  setmetatable(values, values_m);

  for k,v in pairs(t) do
    -- serializing all keys and values
    local null = values[k];
    null = values[v];
  end

  -- Writing closure code
  file:write([[
local setupvalue = debug.setupvalue;
local select = select;
local function close(f, ...)
  for i = 1, select("#", ...) do
    setupvalue(f, i, (select(i, ...)));
  end
end]], "\n\n");

  file:write(values());
  file:write("\n");

  for k,v in pairs(t) do
    file:write(name, "[", values[k], "] = ", values[v], "\n");
  end

  file:flush();

  return true;
end
